Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Displaying Rendered • View rawDownload


docs/en/plugins.md e5756d7ab065475a18acdcafc008a767fcff87e0 (e5756d7a) Text, 9.42 KB

Plugins

Plugins extend MeshChatX with extra tools, nav items, and background behaviour. They are capability-gated: a plugin only gets what you grant at install time.

Manage them from Settings → Plugins. Disable every packaged plugin at startup with T383838--disable-plugins or T383838MESHCHAT_DISABLE_PLUGINS=true.

What plugins can do

• Add a row on the Tools page
• Add an item in the main Navigation sidebar
• React to mesh events (announces, RNS link traffic)
• Call narrowly declared backend managers (path table, debug log, bug reports, RNS links)
• Keep a private key-value store (T383838storage: isolated)
• Optionally fetch clearnet HTTP (T383838network: fetch), still subject to Privacy mode

Plugins cannot rewrite core MeshChatX. They do not get open-ended filesystem or process control unless you opt into Sideband Python plugins (see below).

Runtimes

┌────────────────┬───────────────────────────┬─────────────────────────────────────────────────┐
│ Runtime │ Where it runs │ Trust level │
├────────────────┼───────────────────────────┼─────────────────────────────────────────────────┤
│ Frontend JS │ Browser Web Worker │ Medium. Sandboxed worker, capability grants │
│ Backend WASM │ T383838wasmtime on the server │ Medium. Fuel-metered, capability-gated host │
│ Backend Python │ In-process with MeshChatX │ High. Permission-checked host, still powerful │
│ Sideband T383838*.py │ In-process, flat files │ Highest. Opt-in danger switch, full host access │
└────────────────┴───────────────────────────┴─────────────────────────────────────────────────┘

A packaged plugin can ship frontend only, backend only, or both.

Install flow

T282828
Pick ZIP or .wasm file in Settings → Plugins
|
--> Preview (permissions, URLs, signature, findings)
|
--> You grant or deny each capability
|
--> Optional: trust a valid signer
|
--> Install + integrity hash stored
|
--> Enable
|
--> Frontend Worker loads (if present)
--> Backend WASM / Python activates (if present)

Invalid signatures hard-block install. Unsigned packages are allowed. Present-but-broken signatures are not.

After install, MeshChatX hashes the on-disk tree. If files change outside the app, the plugin is auto-disabled as tampered.

Bundled example: Bug Reports

T383838com.meshchatx.mcx-bugs ships with MeshChatX. It adds a Bug Reports tool for sending redacted debug logs to an T383838mcx-bugs-v1 collector, or running a collector yourself.

Layout:

T282828
mcx-bugs/
plugin.json
frontend/main.js
backend/main.py
locales/en.json

Use it as the reference package when building your own.

Manifest (T383838plugin.json)

Every packaged plugin needs a root T383838plugin.json.

T282828
Tb4b4b4{
Tff7b72"id"Tb4b4b4: Ta5d6ff"com.example.my-plugin"Tb4b4b4,
Tff7b72"version"Tb4b4b4: Ta5d6ff"1.0.0"Tb4b4b4,
Tff7b72"apiVersion"Tb4b4b4: T79c0ff1Tb4b4b4,
Tff7b72"name"Tb4b4b4: Ta5d6ff"My Plugin"Tb4b4b4,
Tff7b72"description"Tb4b4b4: Ta5d6ff"Adds a custom tool."Tb4b4b4,
Tff7b72"frontend"Tb4b4b4: Tb4b4b4{
Tff7b72"entry"Tb4b4b4: Ta5d6ff"frontend/main.js"Tb4b4b4,
Tff7b72"type"Tb4b4b4: Ta5d6ff"js"
Tb4b4b4},
Tff7b72"backend"Tb4b4b4: Tb4b4b4{
Tff7b72"entry"Tb4b4b4: Ta5d6ff"backend/main.py"Tb4b4b4,
Tff7b72"type"Tb4b4b4: Ta5d6ff"python"
Tb4b4b4},
Tff7b72"i18n"Tb4b4b4: Tb4b4b4{
Tff7b72"directory"Tb4b4b4: Ta5d6ff"locales"Tb4b4b4,
Tff7b72"defaultLocale"Tb4b4b4: Ta5d6ff"en"
Tb4b4b4},
Tff7b72"contributes"Tb4b4b4: Tb4b4b4{
Tff7b72"navItems"Tb4b4b4: Tb4b4b4[
Tb4b4b4{
Tff7b72"id"Tb4b4b4: Ta5d6ff"my-plugin"Tb4b4b4,
Tff7b72"route"Tb4b4b4: Tb4b4b4{ Tff7b72"name"Tb4b4b4: Ta5d6ff"plugin-my-plugin" Tb4b4b4},
Tff7b72"icon"Tb4b4b4: Ta5d6ff"puzzle"Tb4b4b4,
Tff7b72"labelKey"Tb4b4b4: Ta5d6ff"nav"
Tb4b4b4}
Tb4b4b4],
Tff7b72"toolsPageEntries"Tb4b4b4: Tb4b4b4[
Tb4b4b4{
Tff7b72"name"Tb4b4b4: Ta5d6ff"my-plugin"Tb4b4b4,
Tff7b72"route"Tb4b4b4: Tb4b4b4{ Tff7b72"name"Tb4b4b4: Ta5d6ff"plugin-my-plugin" Tb4b4b4},
Tff7b72"icon"Tb4b4b4: Ta5d6ff"puzzle"Tb4b4b4,
Tff7b72"titleKey"Tb4b4b4: Ta5d6ff"title"Tb4b4b4,
Tff7b72"descriptionKey"Tb4b4b4: Ta5d6ff"description"
Tb4b4b4}
Tb4b4b4]
Tb4b4b4},
Tff7b72"permissions"Tb4b4b4: Tb4b4b4{
Tff7b72"hooks"Tb4b4b4: Tb4b4b4[Ta5d6ff"announce.received"Tb4b4b4],
Tff7b72"managers"Tb4b4b4: Tb4b4b4[Ta5d6ff"destinationPath.read"Tb4b4b4],
Tff7b72"storage"Tb4b4b4: Ta5d6ff"isolated"Tb4b4b4,
Tff7b72"network"Tb4b4b4: Ta5d6ff"none"
Tb4b4b4}
Tb4b4b4}


Notes:

• T383838id is reverse-DNS style and must stay stable across versions
• T383838apiVersion is currently T3838381
• Plugin strings live in the plugin bundle (T383838locales/{locale}.json), not core T383838en.json
• T383838contributes wires UI slots through the frontend registries

Permissions

Nothing is available unless it is declared in the manifest and granted in the install dialog.

Hooks

┌───────────────────┬─────────────────────────────────────────────────────────┐
│ Hook │ When it fires │
├───────────────────┼─────────────────────────────────────────────────────────┤
│ T383838announce.received │ A Reticulum announce arrives │
│ T383838rns.link.event │ Generic RNS Link traffic (T383838packet_received, T383838link_closed) │
└───────────────────┴─────────────────────────────────────────────────────────┘

Hook events reach the UI as WebSocket T383838plugin.event frames, then into the plugin Worker.

Managers

┌──────────────────────┬───────────────────────────────┐
│ Manager │ Purpose │
├──────────────────────┼───────────────────────────────┤
│ T383838destinationPath.read │ Read the Reticulum path table │
│ T383838debugLog.read │ Read redacted debug logs │
│ T383838bugReport.* │ Bug report / collector APIs │
│ T383838rnsLink.open │ Open or reuse an RNS link │
│ T383838rnsLink.identify │ Identify on a cached link │
│ T383838rnsLink.request │ Request/response on a link │
│ T383838rnsLink.send │ Send a raw link packet │
│ T383838rnsLink.close │ Tear down a cached link │
└──────────────────────┴───────────────────────────────┘

Call managers from a plugin with T383838POST /api/v1/plugins/{id}/invoke and T383838method: "callManager". Details for the link transport are in RNS Link API.

Storage and network

┌───────────────────┬───────────────────────────────────────────────────────────────────────┐
│ Permission │ Effect │
├───────────────────┼───────────────────────────────────────────────────────────────────────┤
│ T383838storage: isolated │ Private key-value store in the MeshChatX database │
│ T383838storage: none │ No plugin storage │
│ T383838network: fetch │ Outbound HTTP allowed (still blocked by Privacy mode when that is on) │
│ T383838network: none │ No clearnet fetch │
└───────────────────┴───────────────────────────────────────────────────────────────────────┘

Install preview also scans plugin files for external T383838http:// / T383838https:// URLs and shows them before you grant network access.

How a frontend plugin runs

T282828
Settings enable plugin
|
--> PluginHost loads /api/v1/plugins
|
--> Fetch frontend entry as text
|
--> Spawn pluginWorker.js (module Worker)
|
--> Register nav / tools contributions
|
--> Subscribe to plugin.event on /ws (if hooks granted)
|
--> Worker may invoke backend via /api/v1/plugins/{id}/invoke

The Worker talks to the host with typed messages (T383838init, T383838event, T383838request). The host never gives the Worker a raw privileged API.

How a backend plugin runs

T282828
Enable plugin
|
+--> type: wasm --> load into wasmtime, fuel + host caps
|
+--> type: python --> import entry, call activate(host)
|
--> Hooks fan out from PluginManager
|
--> invoke(method, args) for RPC from the UI Worker

Python host surface (permission-checked):

• T383838host.log(message)
• T383838host.call_manager(capability, args)
• T383838host.storage_get(key) / T383838host.storage_set(key, value)
• T383838host.network_fetch_allowed()

Packaging and signing

Distribute as:

1. ZIP with T383838plugin.json and assets
2. WASM bundle (single T383838.wasm with embedded manifest / files / optional signature)

Signature file for ZIP/dir packages: T383838meshchatx.plugin.rsg

WASM custom sections:

T282828
meshchatx.plugin --> embedded plugin.json
meshchatx.files --> embedded text assets
meshchatx.signature --> RSG over payload without this section

Canonical ZIP signing uses sorted paths and fixed 1980-01-01 mtimes. The signature file itself is excluded from the signed payload.

Sign and verify with:

T282828
python3 scripts/sign-plugin.py sign-dir ./my-plugin --identity <rnid>
python3 scripts/sign-plugin.py verify-dir ./my-plugin
python3 scripts/sign-plugin.py sign-zip ./my-plugin.zip --identity <rnid>
python3 scripts/sign-plugin.py sign-wasm ./plugin.wasm --identity <rnid>
python3 scripts/sign-plugin.py sign-py ./legacy_plugin.py --identity <rnid>

Trust status in the UI:

T282828
No .rsg present
--> Unsigned (install allowed)

Valid .rsg, signer unknown
--> Signed (you can add to Trusted Publishers)

Valid .rsg, signer in Trusted Publishers
--> Trusted

Broken / mismatched .rsg
--> Invalid (install blocked)

Sideband-compatible plugins

Legacy Sideband-style flat T383838*.py files are separate from packaged ZIP/WASM plugins.

T282828
Settings → Plugins → Sideband
|
--> Confirm danger prompt
|
--> Set directory of *.py files
|
--> Optional filename.py.rsg next to each script
|
--> Reload

These run in-process with full host access. They are not ZIP-permission gated. Keep the master switch off unless you trust every file in that directory.

Operator tips

• Prefer signed packages from publishers you added yourself
• Deny T383838network: fetch unless the plugin truly needs clearnet
• Prefer WASM backends over Python when you can
• Use T383838--disable-plugins when diagnosing weird UI or backend behaviour
• Treat Sideband plugins like running arbitrary local scripts

See also

Tools and utilities for the Tools page and contribution overview
RNS Link API for T383838rnsLink.* and T383838rns.link.event
Architecture and design for the plugin runtime overview
Identities, privacy, and security for signing and Privacy mode


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────